package com.bubblegumproject.ogo.remote.tunnel;

import com.bubblegumproject.ogo.action.Action;
import com.bubblegumproject.ogo.action.Reactor;
import com.bubblegumproject.ogo.remote.OgoRemoteException;
import com.bubblegumproject.ogo.remote.io.ActionPacketFactory;
import com.bubblegumproject.ogo.remote.io.DefaultActionPacketFactory;
import com.bubblegumproject.ogo.remote.io.ActionPacket;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import org.apache.log4j.Logger;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.HttpURLConnection;

/**
 * Exports an Agent to be available via HTTP using the protocol defined by
 * {@link com.bubblegumproject.ogo.remote.tunnel.HttpTunnel}.
 *
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
public class HttpTunnelEndPoint {

    private Reactor reactor;

    private String reactorName;

    private InetSocketAddress hostAddress;

    private HttpServer httpServer;

    private ActionPacketFactory packetFactory = new DefaultActionPacketFactory();

    private static final Logger log = Logger.getLogger(HttpTunnelEndPoint.class);

    public HttpTunnelEndPoint(String reactorName, Reactor reactor, InetSocketAddress hostAddress) {
        assert (reactorName != null && reactor != null && hostAddress != null);
        this.reactorName = reactorName;
        this.reactor = reactor;
        this.hostAddress = hostAddress;
    }

    public void start() {
        try {
            httpServer = HttpServer.create(hostAddress, 25);
            String contextPath = "/" + reactorName;
            httpServer.createContext(contextPath, new ActionHttpHandler());
            httpServer.start();

            log.info(this + " started context " + contextPath + " on " + hostAddress + " for reactor " + reactor);
        } catch (IOException ioe) {
            throw new OgoRemoteException(this + " failed to start http server", ioe);
        }
    }

    public void stop() {
        httpServer.stop(30);

        log.info(this + " stopped http server for reactor " + reactor);
    }

    private TunnelRequest read(InputStream in) throws IOException {
        ObjectInputStream objectIn = new ObjectInputStream(in);
        try {
            return (TunnelRequest) objectIn.readObject();
        } catch (ClassNotFoundException cnfe) {
            throw new IOException(this + " failed to read request", cnfe);
        }
    }

    private void write(TunnelResponse response, OutputStream output) throws IOException {
        ObjectOutputStream objectOut = new ObjectOutputStream(output);
        objectOut.writeObject(response);
        objectOut.flush();
    }

    private class ActionHttpHandler implements HttpHandler {
        public void handle(HttpExchange t) throws IOException {
            try {
                log.debug(this + " handling HTTP request " + t.getRequestURI());
                TunnelRequest request = read(t.getRequestBody());
                log.debug(this + " handling request " + request);

                TunnelResponse response;
                try {
                    log.debug(this + " received input ActionPacket " + request.getInputPacket());
                    Action inputAction = packetFactory.parseInputPacket(request.getInputPacket());
                    Action responseAction = reactor.react(inputAction);
                    ActionPacket outputPacket = packetFactory.createOutputPacket(responseAction);
                    log.debug(this + " sending outputPacket ActionPacket " + outputPacket);
                    response = new TunnelResponse(outputPacket);
                } catch (Throwable error) {
                    // XXX: You can't just send this error back to the client because it may contain exception classes
                    // the client doesn't have
                    log.warn(this + " encountered exception " + error + " when processing request " + request, error);
                    response = new TunnelResponse(error);
                }

                t.sendResponseHeaders(HttpURLConnection.HTTP_OK, 0);
                write(response, t.getResponseBody());
                t.close();
            } catch (Throwable x) {
                log.error("exception escaped handler! ", x);
            }
        }
    }
}
